package com.zxymq.amqp.support;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zxymq.amqp.util.json.JsonMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.util.Assert;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * 引入 TypeReference 便于解析复杂泛型结构的转换 借助jackson
 *
 * @Auther: wfm0105@163.com
 * @Date: 2018/8/9 10:36
 * @Description:
 */
public class DefaultJackson2JsonMessageConverter extends Jackson2JsonMessageConverter {
    private static Log log = LogFactory.getLog(DefaultJackson2JsonMessageConverter.class);
    private JsonMapper jsonMapper;
    private TypeReference targetTypeReference;

    public TypeReference getTypeReference() {
        return targetTypeReference;
    }

    public void setTypePrecedence(TypeReference targetTypeReference) {
        Assert.notNull(targetTypeReference, "'targetTypeReference' must not be null");
        this.targetTypeReference = targetTypeReference;
    }

    public DefaultJackson2JsonMessageConverter() {
        this(new JsonMapper());
    }

    public DefaultJackson2JsonMessageConverter(JsonMapper jsonMapper) {
        Assert.notNull(new ObjectMapper(), "'jsonObjectMapper' must not be null");
        this.jsonMapper = jsonMapper;
        this.jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    }

    @Override
    public Object fromMessage(Message message)
            throws MessageConversionException {
        Object content = null;
        MessageProperties properties = message.getMessageProperties();
        if (properties != null) {
            String contentType = properties.getContentType();
            if (contentType != null && contentType.contains("json")) {
                String encoding = properties.getContentEncoding();
                if (encoding == null) {
                    encoding = getDefaultCharset();
                }
                try {
                    if (null!=this.targetTypeReference) {
                        content = convertBytesToObject(message.getBody(),
                                encoding, targetTypeReference);
                    } else if (getClassMapper() == null) {
                        JavaType targetJavaType = getJavaTypeMapper()
                                .toJavaType(message.getMessageProperties());
                        content = convertBytesToObject(message.getBody(),
                                encoding, targetJavaType);
                    } else {
                        Class<?> targetClass = getClassMapper().toClass(
                                message.getMessageProperties());
                        content = convertBytesToObject(message.getBody(),
                                encoding, targetClass);
                    }
                } catch (IOException e) {
                    throw new MessageConversionException(
                            "Failed to convert Message content", e);
                }
            } else {
                if (log.isWarnEnabled()) {
                    log.warn("Could not convert incoming message with content-type ["
                            + contentType + "]");
                }
            }
        }
        if (content == null) {
            content = message.getBody();
        }
        return content;
    }

    private Object convertBytesToObject(byte[] body, String encoding, JavaType targetJavaType) throws IOException {
        String contentAsString = new String(body, encoding);
        return this.jsonMapper.readValue(contentAsString, targetJavaType);
    }

    private Object convertBytesToObject(byte[] body, String encoding, Class<?> targetClass) throws IOException {
        String contentAsString = new String(body, encoding);
        return this.jsonMapper.readValue(contentAsString, this.jsonMapper.constructType(targetClass));
    }

    private Object convertBytesToObject(byte[] body, String encoding, TypeReference targetTypeReference) throws IOException {
        String contentAsString = new String(body, encoding);
        return this.jsonMapper.readValue(contentAsString, targetTypeReference);
    }
}
